#include "value.hpp"
#include <vector>
#include <iostream>
#include <sstream>
#include <boost/algorithm/string.hpp>

using namespace std;
using namespace boost;

namespace BMD
{
  /** Grabs a value of the given type, throwing an error
      with the given string as the expected type if necessary
  */
  template <typename T>
  T Value::get(string s) const
  {
    try
    {
      return boost::apply_visitor(assert_type_visitor<T>(), *this);
    }
    catch (null_value_error e)
    {
      throw null_value_error("uninitialized value used, should be a " + s);
    }
    catch (type_error e)
    {
      throw type_error("expected value to be a " + s + ", not a " + this->get_type());
    }
  }

  /** Grabs the integer value if the object contains
      an integer. Otherwise, it throws a type_error exception
  */
  int Value::get_int() const
  {
    return get<int>("int");
  }
  
  /** Grabs the double value if the object contains
      a double. Otherwise, it throws a type_error exception
  */
  double Value::get_float() const 
  {
    return get<double>("float");
  }

  /** Grabs the string value if the object contains
      a string. Otherwise, it throws a type_error exception
  */
  string Value::get_string() const
  {
    return get<string>("string");
  }

  /** Grabs the list value if the object contains
      a list. Otherwise, it throws a type_error exception
  */
  ListRef Value::get_list() const
  { 
    return get<ListRef>("list");
  }

  /** Grabs a list value, first ensuring that the list is not empty. 
   * @throws type_error
   * @throws empty_list_error
   */
  ListRef Value::get_nonempty_list() const
  {
    ListRef l = get<ListRef>("non-empty list");
    if (l.get() == 0)
      throw empty_list_error("expected a non-empty list, not a " + get_type());
    return l;
  }

  /** Grabs a block value */
  BlockRef Value::get_block() const
  {
    return get<BlockRef>("block");
  }

  /** Grabs a block value with the array tag
   * @throws type_error
   */
  BlockRef Value::get_array() const
  {
    BlockRef arr = get<BlockRef>("array");
    if (arr->get_tag() != Block::Array)
      throw type_error("Block does not have correct tag");
    return arr;
  }

  /** Grabs a block value with the struct tag
   * @throws type_error
   */
  BlockRef Value::get_struct() const
  {
    BlockRef str = get<BlockRef>("struct");
    if (str->get_tag() != Block::Struct)
      throw type_error("Block does not have correct tag");
    return str;
  }

  /** Grabs a block value with the reference tag
   * @throws type_error
   */
  BlockRef Value::get_reference() const
  {
    BlockRef ref = get<BlockRef>("reference");
    if (ref->get_tag() != Block::Reference)
      throw type_error("Block does not have correct tag");
    return ref;
  }  

  /** Returns a string name for the value's type */
  string Value::get_type() const
  {
    return boost::apply_visitor(typename_visitor(), *this);
  }

  void Value::print(ostream& o, bool print_full) const
  {
    boost::apply_visitor(output_visitor(o, print_full), *this);
  }
  
  ostream& operator <<(ostream& o, const Value& v)
  {
    boost::apply_visitor(output_visitor(o), v);
    return o;
  }

  //! deep copies the value
  Value Value::clone() const
  {
    return boost::apply_visitor(clone_visitor(false), *this);
  }

  Value Value::deep_clone() const
  {
    return boost::apply_visitor(clone_visitor(true), *this);
  }

  string Value::to_string() const
  {
    ostringstream oss;
    boost::apply_visitor(output_visitor(oss), *this);
    return oss.str();
  }

  bool Value::operator==(const Value& v) const
  {
    return boost::apply_visitor(equals_visitor(), *this, v);
  }

  /*************************************
   * Parsing 
   ************************************/  
  /** parses an value from a string (integer, double, or string) */
  Value Value::parse_value(string s)
  {
    if (istarts_with(s, "\"") && iends_with(s, "\""))
    {
      // unescape all escaped sequences
      string inside = s.substr(1, s.size() - 2);
      replace_all(inside, "\\\\", "\r");
      replace_all(inside, "\\t", "\t");
      replace_all(inside, "\\n", "\n");
      replace_all(inside, "\\\"", "\"");
      replace_all(inside, "\r", "\\");
      return Value(inside);
    }
    else
      return parse_numeric_value(s);
  }
  
  /** parses a numeric value (integer or double) 
      from a string */
  Value Value::parse_numeric_value(string s)
  {
    if (s.find('.', 0) != string::npos || 
        s.find('e', 0) != string::npos || 
        s.find('E', 0) != string::npos)
      return Value(atof(s.c_str()));
    else
      return Value(atoi(s.c_str()));
  }

  /*************************************
   * Block methods
   ************************************/
  Block::Block(vector<Value>& _v, Tag _tag) : tag(_tag)
  {
    size = _v.size();
    values = shared_array<Value>(new Value[size]);
    copy(_v.begin(), _v.end(), values.get());
  }

  //! Constructs an array given a heap-allocated array of values
  Block::Block(Value* v, int _size, Tag _tag) : tag(_tag)
  {
    // act based on the validity of the value pointer
    if (!v || (_size <= 0))
    {
      size = 0;
      values = shared_array<Value>();
    }
    else
    {
      size = _size;
      values = shared_array<Value>(v);
    }
  }

  Block::Block(int _size, Tag _tag) : tag(_tag)
  {
    size = (_size > 0) ? _size : 0;
    values = shared_array<Value>(new Value[size]);
  }
  
  int Block::assert_in_range(int i) const
  {
    if (i < 0 || (unsigned int)i >= size)
    {
      ostringstream o;
      o << i << " is not a valid index";
      throw std::range_error(o.str());
    }
    return i;
  }

  Value Block::get_elem(int idx) const
  {
    return values[assert_in_range(idx)];
  }

  void Block::set_elem(int idx, const Value& v) 
  {
    values[assert_in_range(idx)] = v;
  }

  /*************************************
   * List methods
   ************************************/
  unsigned int List::length()
  {
    int size = 1;
    ListRef curr = cdr;
    while (curr.get() != 0)
    {
      ++size;
      curr = curr->cdr;
    }
    return size;
  }

  ListRef List::from_vector(const vector<Value>& args)
  {
    if (args.size() == 0)
      return ListRef();

    // go backwards in the list, consing on elements
    ListRef ret;
    for (vector<Value>::const_reverse_iterator itr = args.rbegin(); itr != args.rend(); ++itr)
      ret = ListRef(new List(*itr, ret));

    return ret;
  }
}
